package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.list.dao.GoodsDao;
import com.atguigu.gmall.list.service.ListService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuAttrValue;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.range.Range;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: 李旭
 * @Time: 15:48
 * @Description: 搜索
 * 添加
 * 修改
 * 删除
 */
@Service
public class ListServiceImpl implements ListService {
    //Spring官方封装  //ES 官方近期高级别的客户端
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;//搜索方面性能极佳
    //ES 官方近期高级别的客户端
    @Autowired
    private RestHighLevelClient restHighLevelClient;//搜索方面性能极佳

    @Autowired
    private GoodsDao goodsDao;
    @Autowired
    private ProductFeignClient productFeignClient;

    //上架之导入数据
    @Override
    public void onSale(Long skuId) {
        Goods goods = new Goods();
        //1:sku_info 库存表
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        goods.setId(skuInfo.getId());
        goods.setTitle(skuInfo.getSkuName());
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        //实时价格
        BigDecimal price = productFeignClient.getPrice(skuId);
        goods.setPrice(price.doubleValue());
        //时间
        Calendar instance = Calendar.getInstance();//当前时间 美国 中国
        //instance.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        goods.setCreateTime(instance.getTime());// new Date()
        //2:品牌表
        BaseTrademark baseTrademark = productFeignClient.getBaseTrademark(skuInfo.getTmId());
        goods.setTmId(baseTrademark.getId());
        goods.setTmName(baseTrademark.getTmName());
        goods.setTmLogoUrl(baseTrademark.getLogoUrl());
        //3:三级分类视图
        BaseCategoryView baseCategoryView = productFeignClient.
                getBaseCategoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(baseCategoryView.getCategory1Id());
        goods.setCategory1Name(baseCategoryView.getCategory1Name());
        goods.setCategory2Id(baseCategoryView.getCategory2Id());
        goods.setCategory2Name(baseCategoryView.getCategory2Name());
        goods.setCategory3Id(baseCategoryView.getCategory3Id());
        goods.setCategory3Name(baseCategoryView.getCategory3Name());
        //4:平台属性表  入参： skuId    库存对应的 平台属性名称 ID 值
        List<SkuAttrValue> skuAttrValueList
                = productFeignClient.getSkuAttrValue(skuId);
        //新办法  偷天换日
        //结果
        List<SearchAttr> attrList = skuAttrValueList.stream().map(skuAttrValue -> {
            SearchAttr searchAttr = new SearchAttr();
            //平台属性ID
            searchAttr.setAttrId(skuAttrValue.getBaseAttrInfo().getId());
            //平台属性名称
            searchAttr.setAttrName(skuAttrValue.getBaseAttrInfo().getAttrName());
            //平台属性值名称
            searchAttr.setAttrValue(skuAttrValue.getBaseAttrValue().getValueName());
            return searchAttr;
        }).collect(Collectors.toList());
        goods.setAttrs(attrList);
        //保存
        goodsDao.save(goods);
    }

    //下架之删除数据
    @Override
    public void cancelSale(Long skuId) {
        //删除
        goodsDao.deleteById(skuId);
    }


    @Autowired
    private RedisTemplate redisTemplate;

    //增加关注热度  增加评论数  增加晒单数 ..
    @Override
    public void hotScore(Long skuId) {
        //ES 索引    "hotScore" : 0, 1  不太正确 事情是对的  做法不正确
        //ES  :  快吗？ 查询快  添加与修改与删除 超级慢 新华字典  上下5000年 教育大心力
        //爬虫

        /**
         * 方案：攒一堆再修改ES索引库
         *    Redis
         *      五大数据类型
         *      String类型（字符串）
         *      Hash类型（散列类型）
         *      List类型（列表类型）
         *      Set类型（集合类型）
         *      zset 类型（有序集合类型）
         *
         *     小明   语文 100分  数学 88分  英语 98分
         *     小张   语文 99分  数学 98分  英语 77分
         *
         *
         *    zset  k(小明)
         *                 k(语文)  score(100分)
         *                 数学 88分
         *                 英语 98分
         *           关注度分
         *    zset  hotScore
         *                 skuId    80
         *                 skuId    70
         *                 skuId    60
         *            评论数分
         *   zset  pinglunScore
         *          *                 skuId    80
         *          *                 skuId    70
         *          *                 skuId    60
         *
         */


        //1:热度分  先保存缓存    分度达到一定阈值 再保存到ES索引库中  10分
        // 参数1：zset  hotScore
        // 参数2：zset  skuId
        // 参数3：zset  score

        //原子性问题
     /*   Double hotScore = redisTemplate.opsForZSet().score("hotScore", skuId);
        hotScore++;
        redisTemplate.opsForZSet().add("hotScore",skuId,hotScore);*/

        //此时此刻多少分
        Double hotScore = redisTemplate.opsForZSet().incrementScore("hotScore", skuId, 1);
        if (hotScore % 10 == 0) {
            //1:查询
            Optional<Goods> byId = goodsDao.findById(skuId);
            Goods goods = byId.get();
            goods.setHotScore(Math.round(hotScore));// 5.5  6
            //2：保存
            goodsDao.save(goods);//  判断id 相同是修改（底层 是先删除 再添加）  id不同是添加

        }

    }

    //开始搜索  最难的地方来了
    @Override
    public SearchResponseVo list(SearchParam searchParam) {
        //1:构建条件请求对象
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        try {
            //2：执行搜索
            SearchResponse searchResponse = restHighLevelClient.
                    search(searchRequest, RequestOptions.DEFAULT);
            //3:解析结果
            SearchResponseVo vo = parseSearchResponse(searchResponse);
            //当前页
            vo.setPageNo(searchParam.getPageNo());
            //每页数
            vo.setPageSize(searchParam.getPageSize());
            //总页数 =  （总条数 + 每页数 -1）/每页数
            long totalPages =  (vo.getTotal() + vo.getPageSize() - 1)/ vo.getPageSize();
            vo.setTotalPages(totalPages);
            return vo;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    //解析搜索结果
    private SearchResponseVo parseSearchResponse(SearchResponse searchResponse) {
        SearchResponseVo vo = new SearchResponseVo();

        SearchHits hits = searchResponse.getHits();
        //1:总条数
        long totalHits = hits.getTotalHits();
        System.out.println("总条数:" + totalHits);
        vo.setTotal(totalHits);
        //2:商品结果集
        SearchHit[] hits1 = hits.getHits();
        //商品结果集
        List<Goods> goodsList = Arrays.stream(hits1).map(searchHit -> {
            String sourceAsString = searchHit.getSourceAsString();
            Goods goods = JSONObject.parseObject(sourceAsString, Goods.class);
            System.out.println("无高亮的名称：" + goods.getTitle());
            //解析出高亮的名称 将无高亮的名称替换掉的   空指针异常
            Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
            if(null != highlightFields && highlightFields.size() > 0){
                HighlightField title = highlightFields.get("title");
                goods.setTitle(title.fragments()[0].toString());
            }
            return goods;
        }).collect(Collectors.toList());
        vo.setGoodsList(goodsList);
        //3:品牌集合
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");

        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseTmVo tmVo = new SearchResponseTmVo();
            //品牌Id
            tmVo.setTmId(Long.parseLong(bucket.getKeyAsString()));

            Aggregations aggregations = ((Terms.Bucket) bucket).getAggregations();
            //品牌名称
            ParsedStringTerms tmNameAgg = aggregations.get("tmNameAgg");
            tmVo.setTmName(tmNameAgg.getBuckets().get(0).getKeyAsString());
            //品牌Logo
            ParsedStringTerms tmLogoUrlAgg = aggregations.get("tmLogoUrlAgg");
            tmVo.setTmLogoUrl(tmLogoUrlAgg.getBuckets().get(0).getKeyAsString());
            return tmVo;
        }).collect(Collectors.toList());
        vo.setTrademarkList(trademarkList);

        //4:平台集合
        ParsedNested attrsAgg = (ParsedNested) aggregationMap.get("attrsAgg");
        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> attrsList = attrIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
            //平台属性ID
            attrVo.setAttrId(Long.parseLong(bucket.getKeyAsString()));
            //平台属性名称
            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
            attrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());
            //平台属性值列表
            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
            attrVo.setAttrValueList(attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).
                    collect(Collectors.toList()));
            return attrVo;
        }).collect(Collectors.toList());
        vo.setAttrsList(attrsList);
        return vo;
    }


    //构建条件对象  几十上百行代码
    public SearchRequest buildSearchRequest(SearchParam searchParam) {

        //1:构建搜索请求对象 GET goods/_search
        SearchRequest searchRequest = new SearchRequest();
        //2:构建搜索资源条件对象
        /**
         * DSL语句
         *  {
         *     query: {
         *       match_all:{}
         *      }
         *  }
         */
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        //组合条件对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //1、关键词
        String keyword = searchParam.getKeyword();
        if (!StringUtils.isEmpty(keyword)) {
            //匹配 指定的域及值  华为手机
            boolQueryBuilder.must(
                    QueryBuilders.matchQuery("title", keyword).operator(Operator.AND)
            );
        } else {
            //匹配全部 ES 官方 工具类
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        }
        //2、过滤条件
        //2.1 品牌  品牌ID:品牌名称
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)) {
            String[] t = StringUtils.split(trademark, ":");
            // 0 : 品牌ID  1: 品牌名称
            boolQueryBuilder.filter(QueryBuilders.termQuery("tmId", t[0]));
        }
        //2.2 一二三级分类
        Long category1Id = searchParam.getCategory1Id();
        if (null != category1Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", category1Id));
        }
        Long category2Id = searchParam.getCategory2Id();
        if (null != category2Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", category2Id));
        }
        Long category3Id = searchParam.getCategory3Id();
        if (null != category3Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", category3Id));
        }
        //2.3 平台属性 （难）
        String[] props = searchParam.getProps();
        if (null != props && props.length > 0) {
            //第二个Boolean
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            for (String prop : props) {
                //props=23:4G:运行内存  平台属性ID:平台属性值:平台属性名称
                String[] p = prop.split(":");
                if (null != p && p.length == 3) {
                    //第三个Boolean
                    BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", p[0]));
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue", p[1]));

                    boolQuery.must(QueryBuilders.nestedQuery(
                            "attrs", subBoolQuery, ScoreMode.None));
                }
            }
            //第一个
            boolQueryBuilder.filter(boolQuery);
        }
        //设置组合条件对象
        searchSourceBuilder.query(boolQueryBuilder);
        //3、排序
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)) {
            //order :  1:asc   1:综合  2：价格
            String[] o = order.split(":");
            String name = null;
            switch (o[0]) {
                case "1":
                    name = "hotScore";
                    break;
                case "2":
                    name = "price";
                    break;
            }
            searchSourceBuilder.sort(name,
                    o[1].equals("asc") ? SortOrder.ASC : SortOrder.DESC);
        } else {
            //没有排序字段   默认按照综合 由高到低  降序
            searchSourceBuilder.sort("hotScore", SortOrder.DESC);
        }
        //4、分页
        Integer pageNo = searchParam.getPageNo();
        Integer pageSize = searchParam.getPageSize();
        searchSourceBuilder.from((pageNo - 1) * pageSize);// limit 开始行
        searchSourceBuilder.size(pageSize);// limit 开始行，每页数

        //分组  难  桶聚合
        //     select name id,group by name  严格模式    指标聚合 max min sum ..
        //5:聚合 品牌   页面显示  品牌ID 品牌名称 品牌的Logo
        //复杂聚合  父 2个儿子
        searchSourceBuilder.aggregation(
                AggregationBuilders.terms("tmIdAgg").field("tmId")
                        .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                        .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl")));

        //6:聚合  平台属性入口 父       平台属性ID 子  其它作为2个孙子
        searchSourceBuilder.aggregation(
                AggregationBuilders.nested("attrsAgg", "attrs")
                        .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                                .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                                .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))));

        //7:高亮  <font color='red'>手机</font>  <span style='color:green'>手机</span>
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder
                 .field("title")
                 .preTags("<font color='red'>")
                 .postTags("</font>");
        searchSourceBuilder.highlighter(highlightBuilder);
        HighlightBuilder highlighter = searchSourceBuilder.highlighter();
        highlighter.preTags("<span style='color:green'>")
                   .postTags("</span>");

        System.out.println("DSL:" + searchSourceBuilder.toString());
        //修改了BUG
        searchRequest.source(searchSourceBuilder);
        //设置索引库
        searchRequest.indices("goods");
        //设置type
        //searchRequest.types("info");
        //返回值
        return searchRequest;
    }

}
